package com.stary.beetlsql.generator;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.beetl.core.Template;
import org.beetl.sql.core.JavaType;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.SQLReady;
import org.beetl.sql.core.db.ColDesc;
import org.beetl.sql.core.db.TableDesc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.stary.beetlsql.generator.config.Constants;
import com.stary.beetlsql.generator.config.DataSourceConfig;
import com.stary.beetlsql.generator.config.IDbQuery;
import com.stary.beetlsql.generator.config.PackageConfig;
import com.stary.beetlsql.generator.config.StrategyConfig;
import com.stary.beetlsql.generator.config.TemplateConfig;
import com.stary.beetlsql.generator.config.rules.DBType;
import com.stary.beetlsql.generator.config.rules.IDType;
import com.stary.beetlsql.generator.util.StringUtils;

/**
 * <p>生成器</p>
 * @author stary {@link stary1993@qq.com}
 * @since 2019-7-24
 */
public class BeetlsqlGenerator {

	private static final Logger logger = LoggerFactory.getLogger(BeetlsqlGenerator.class);
	/**
	 * maven src/main/java目录
	 */
	private static final String MAVEN_MAIN_JAVA = "src" + File.separator + "main" + File.separator + "java";
	
	/**
	 * 输出路径
	 */
	private String outDir = "e:\\gen";
	/**
	 * 作者
	 */
	private String author = "stary";
	/**
	 * 打印到控制台
	 */
	private boolean display = false;

	/**
	 * 数据源配置
	 */
	private DataSourceConfig dataSource;
	/**
	 * 包配置
	 */
	private PackageConfig packageInfo;
	/**
	 * 策略配置
	 */
	private StrategyConfig strategy;
	/**
	 * 模板配置
	 */
	private TemplateConfig template;

	/**
	 * 自定义参数
	 */
	private Map<String, Object> extParams;

	public String getOutDir() {
		return outDir;
	}

	public void setOutDir(String outDir) {
		this.outDir = outDir;
	}

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public boolean isDisplay() {
		return display;
	}

	public void setDisplay(boolean display) {
		this.display = display;
	}

	public DataSourceConfig getDataSource() {
		return dataSource;
	}

	public BeetlsqlGenerator setDataSource(DataSourceConfig dataSource) {
		this.dataSource = dataSource;
		return this;
	}

	public PackageConfig getPackageInfo() {
		return packageInfo;
	}

	public BeetlsqlGenerator setPackageInfo(PackageConfig packageInfo) {
		this.packageInfo = packageInfo;
		return this;
	}

	public StrategyConfig getStrategy() {
		return strategy;
	}

	public BeetlsqlGenerator setStrategy(StrategyConfig strategy) {
		this.strategy = strategy;
		return this;
	}

	public TemplateConfig getTemplate() {
		return template;
	}

	public BeetlsqlGenerator setTemplate(TemplateConfig template) {
		this.template = template;
		return this;
	}

	public Map<String, Object> getExtParams() {
		return extParams;
	}

	public BeetlsqlGenerator setExtParams(Map<String, Object> extParams) {
		this.extParams = extParams;
		return this;
	}

	/**
	 * 初始化模板所有参数
	 * @param sqlManager
	 * @param genTables
	 */
	@SuppressWarnings("rawtypes")
	public Map<String, Map<String, Object>> initTemplateCfgs(SQLManager sqlManager, Set<String> genTables) {
		Map<String, Map<String, Object>> allCfg = new HashMap<>();
		Map<String, Object> cfg = null;
		for (String tableName : genTables) {
			cfg = new HashMap<>();
			cfg.put("author", this.author);
			if (this.extParams != null) {
				cfg.put(Constants.OUT_CONFIG, this.extParams); // 自定义参数
			}
			Map<String, Object> packageInfo = new HashMap<>();
			packageInfo.put("parent", this.packageInfo.getParent());
			packageInfo.put("entity", this.packageInfo.getEntity());
			packageInfo.put("mapper", this.packageInfo.getMapper());
			if (this.strategy.isGenService()) {
				packageInfo.put("service", this.packageInfo.getService());
				packageInfo.put("serviceImpl", this.packageInfo.getServiceImpl());			
			}
			packageInfo.put("controller", this.packageInfo.getController());
			cfg.put(Constants.PACKAGE_KEY, packageInfo); // 包配置		
			// 获取表信息
			TableDesc tableDesc = sqlManager.getMetaDataManager().getTable(tableName);		
			Map<String, Object> tableInfo = new HashMap<>(); // 表信息
			Map<String, Object> entityInfo = new HashMap<>(); // entity信息
			tableInfo.put("name", tableDesc.getName()); // 表名
			String tableComment = tableDesc.getRemark();
			IDbQuery dbQuery = this.dataSource.getQuery();
			if (StringUtils.isEmpty(tableComment)) {
				List<Map> list = sqlManager.execute(new SQLReady(dbQuery.tablesSql(tableName)), Map.class);
				if (list != null && list.size() > 0) {
					tableComment = (String) list.get(0).get(dbQuery.tableComment());
				}
			}
			tableInfo.put("comment", tableComment); // 表注释
			// 字段（列）列表
			Set<String> cols = tableDesc.getCols(); 
			List<Map<String, Object>> fields = new ArrayList<>(); //
			Map<String, Object> field = null;
			boolean hasTimestamp = false;
			boolean hasDate = false;
			boolean hasTime = false;
			boolean hasBigDecimal = false;
			boolean hasSQLXML = false;
			for (String col : cols) {
				ColDesc desc = tableDesc.getColDesc(col); //列信息
				field = new HashMap<>();
				boolean isKey = tableDesc.getIdNames().contains(desc.colName);
				field.put("isKey", isKey);
				field.put("name", desc.colName); // 字段（列）名称
				String colComment = desc.remark;
				if (StringUtils.isEmpty(colComment)) {
					List<Map> list = sqlManager.execute(new SQLReady(dbQuery.tableFieldsSql(tableName, desc.colName)), Map.class);
					if (list != null && list.size()>0) {
						colComment = (String) list.get(0).get(dbQuery.fieldComment());
						if (dbQuery.isKeyIdentity(list.get(0))) {
							this.strategy.setIdType(IDType.AutoID);
						} else {
							this.strategy.setIdType(null);
						}
					}
				}
				field.put("comment", colComment); // 字段（列）注释
				String fieldName = sqlManager.getNc().getPropertyName(null, desc.colName);
				field.put("name", fieldName); // 属性名
				String javaType = JavaType.getType(desc.sqlType, desc.size, desc.digit);
				if ("Timestamp".equals(javaType)) {
					if (this.strategy.isPreferDate()) {
						javaType = "Date";
					} else {
						hasTimestamp = true;						
					}
				}
				if ("Time".equals(javaType)) {
					if (this.strategy.isPreferDate()) {
						javaType = "Date";
					} else {
						hasTime = true;					
					}					
				}
				if ("Date".equals(javaType)) {
					hasDate = true;	
				}
				if ("Double".equals(javaType)) {
					if (this.strategy.isPreferBigDecimal()) {
						javaType = "BigDecimal";
					}
				}
				if ("BigDecimal".equals(javaType)) {					
					hasBigDecimal = true;
				}
				if ("SQLXML".equals(javaType)) {
					hasSQLXML = true;
				}
				field.put("type", javaType); // 属性类型
				field.put("methodName", getMethodName(fieldName)); // getter setter方法名
				fields.add(field);
			}
			entityInfo.put("hasTimestamp", hasTimestamp);					
			entityInfo.put("hasDate", hasDate);					
			entityInfo.put("hasTime", hasTime);					
			entityInfo.put("hasBigDecimal", hasBigDecimal);					
			entityInfo.put("hasSQLXML", hasSQLXML);		
			cfg.put(Constants.TABLE_KEY, tableInfo);
			String className = sqlManager.getNc().getClassName(tableDesc.getName());
			String[] tablePrefix = this.strategy.getTablePrefix();
			if (tablePrefix != null && tablePrefix.length > 0) { // 忽略前缀
				for (String tp : tablePrefix) {		
					className = className.replaceFirst(StringUtils.underlineToCamel2(tp.toLowerCase()), "");				
				}
			}
			entityInfo.put("className", className); // 类名
			entityInfo.put("fields", fields); // 字段信息
			if (!DBType.ORACLE.equals(this.dataSource.getDbType())) {
				this.strategy.setEnableTableSeqIDAnnotation(false);
			} else {
				this.strategy.setIdType(IDType.SeqID);
			}
			entityInfo.put("entityLombokModel", this.strategy.isEntityLombokModel());
			entityInfo.put("enableTableSeqIDAnnotation", this.strategy.isEnableTableSeqIDAnnotation());
			entityInfo.put("tableTableSeqIDNamePrefix", this.strategy.getTableTableSeqIDNamePrefix());
			entityInfo.put("tableTableSeqIDNameSuffix", this.strategy.getTableTableSeqIDNameSuffix());
			entityInfo.put("idType", this.strategy.getIdType());
			if (!StringUtils.isEmpty(this.strategy.getSuperEntityClass())) {
				entityInfo.put("superEntity", this.strategy.getSuperEntityClass());
			}
			cfg.put(Constants.ENTITY_KEY, entityInfo);
			Map<String, Object> mapperInfo = new HashMap<>(); // mapper信息
			mapperInfo.put("className", className + this.strategy.getMapperSuffix());
			cfg.put(Constants.MAPPER_KEY, mapperInfo);
			Map<String, Object> mdInfo = new HashMap<>(); // md信息
			mdInfo.put("alias", this.strategy.getAlias());
			Properties ps = loadDefaultProperties();
			mdInfo.put("PS",ps.getProperty("DELIMITER_PLACEHOLDER_START"));
			mdInfo.put("PE", ps.getProperty("DELIMITER_PLACEHOLDER_END"));
			mdInfo.put("SS", ps.getProperty("DELIMITER_STATEMENT_START"));
			mdInfo.put("SE", ps.getProperty("DELIMITER_STATEMENT_END"));
			cfg.put(Constants.MD_KEY, mdInfo);
			cfg.put("nc", this.strategy.getNc());
			if (this.strategy.isGenService()) {
				Map<String, Object> serviceInfo = new HashMap<>(); // service信息
				serviceInfo.put("className", className + this.strategy.getServiceSuffix());
				cfg.put("service", serviceInfo);
				Map<String, Object> serviceImplInfo = new HashMap<>(); // serviceImpl信息
				serviceImplInfo.put("className", className + this.strategy.getServiceImplSuffix());
				cfg.put("serviceImpl", serviceImplInfo);			
			}
			Map<String, Object> controllerInfo = new HashMap<>(); // controller信息
			controllerInfo.put("className", className + this.strategy.getControllerSuffix());
			controllerInfo.put("hasService", this.strategy.isGenService());
			controllerInfo.put("restControllerStyle", this.strategy.isRestControllerStyle());
			controllerInfo.put("superController", this.strategy.getSuperControllerClass());
			cfg.put("controller", controllerInfo);
			allCfg.put(tableName, cfg);
		}		
		return allCfg;
	}

	/**
	 * 生成文件
	 * @param templates 模板组
	 * @param genTables 表组
	 * @param allCfg 模板参数
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	private void genFile(Map<String, Template> templates, Set<String> genTables, 
			Map<String, Map<String, Object>> allCfg) throws IOException {
		Map<String, Object> templateCfg = null;
		for (String tb : genTables) {
			templateCfg = allCfg.get(tb);
			Template et = templates.get(this.packageInfo.getEntity());
			et.binding(templateCfg);
			String etContent = et.render();
			Template mt = templates.get(this.packageInfo.getMapper());
			mt.binding(templateCfg);
			String mtContent = mt.render();
			Template mdt = templates.get(this.packageInfo.getMdPath());
			mdt.binding(templateCfg);
			String mdtContent = mdt.render();
			Template st = null;
			String stContent = "";
			Template sit = null;
			String sitContent = "";
			if (this.strategy.isGenService()) {
				st = templates.get(this.packageInfo.getService());
				st.binding(templateCfg);
				stContent = st.render();
				sit = templates.get(this.packageInfo.getServiceImpl());
				sit.binding(templateCfg);
				sitContent = sit.render();
			}
			Template ct = templates.get(this.packageInfo.getController());
			ct.binding(templateCfg);
			String ctContent = ct.render();
			if (this.display) {
				logger.debug("------------->> 打印代码");
				System.out.println();
				System.out.println(etContent);
				System.out.println();
				System.out.println("<<----------------------------------------------------------------");
				System.out.println();
				System.out.println(mtContent);			
				System.out.println("<<----------------------------------------------------------------");
				System.out.println();
				System.out.println(mdtContent);
				System.out.println();			
				System.out.println("<<----------------------------------------------------------------");
				if (st != null && sit != null) {
					System.out.println();
					System.out.println(stContent);
					System.out.println();			
					System.out.println("<<----------------------------------------------------------------");
					System.out.println();
					System.out.println(sitContent);
					System.out.println();			
					System.out.println("<<----------------------------------------------------------------");
				}
				System.out.println();
				System.out.println(ctContent);
				System.out.println();			
				System.out.println("<<----------------------------------------------------------------");
			}
			logger.debug("------------->> 输出entity文件");
			Map<String, Object> entity = (Map<String, Object>) templateCfg.get(Constants.ENTITY_KEY);
			String className = (String) entity.get("className");
			// entity文件
			String entityPath = this.outDir + File.separator + (this.packageInfo.getParent() + "." + this.packageInfo.getEntity()).replace('.', File.separatorChar);
			FileWriter efw = getFileWriter(entityPath, etContent, className + Constants.JAVA_SUFFIX);
			et.renderTo(efw);
			logger.debug("------------->> 输出mapper文件");
			// mapper文件
			String mapperPath = this.outDir + File.separator + (this.packageInfo.getParent() + "." + this.packageInfo.getMapper()).replace('.', File.separatorChar);
			FileWriter mfw = getFileWriter(mapperPath, mtContent, className + this.strategy.getMapperSuffix() + Constants.JAVA_SUFFIX);
			mt.renderTo(mfw);
			logger.debug("------------->> 输出md文件");
			// md文件
			String mdPath = this.outDir + File.separator + this.packageInfo.getMdPath();
			if (this.outDir.contains(MAVEN_MAIN_JAVA)) { 
				// maven工程 生成在 src/main/resources目录下
				mdPath = this.outDir.replace("java", "resources") + File.separator + this.packageInfo.getMdPath();
			}
			FileWriter mdfw = getFileWriter(mdPath, mdtContent, className.substring(0, 1).toLowerCase() + className.substring(1) + Constants.MD_SUFFIX);
			mdt.renderTo(mdfw);
			if (this.strategy.isGenService()) {
				// service文件
				if (st != null) {
					logger.debug("------------->> 输出service文件");
					String servicePath = this.outDir + File.separator + (this.packageInfo.getParent() + "." + this.packageInfo.getService()).replace('.', File.separatorChar);
					FileWriter sfw = getFileWriter(servicePath, stContent, className + "Service" + Constants.JAVA_SUFFIX);
					st.renderTo(sfw);
				}
				// serviceImpl文件
				if (sit != null) {
					logger.debug("------------->> 输出serviceImpl文件");
					String serviceImplPath = this.outDir + File.separator + (this.packageInfo.getParent() + "." + this.packageInfo.getServiceImpl()).replace('.', File.separatorChar);
					FileWriter sifw = getFileWriter(serviceImplPath, sitContent, className + "ServiceImpl" + Constants.JAVA_SUFFIX);
					sit.renderTo(sifw);
				}
			}
			logger.debug("------------->> 输出controller文件");
			String controllerPath = this.outDir + File.separator + (this.packageInfo.getParent() + "." + this.packageInfo.getController()).replace('.', File.separatorChar);
			FileWriter cfw = getFileWriter(controllerPath, ctContent, className + "Controller" + Constants.JAVA_SUFFIX);
			ct.renderTo(cfw);
			logger.debug("------------->> {} 表代码生成结束", tb);
			logger.debug("<<----------------------------------------------------------------\n");

		}

	}

	private FileWriter getFileWriter(String filePath, String content, String fileName) throws IOException {
		File fileDir  = new File(filePath);
		if (!fileDir.exists()) {
			boolean succ=fileDir.mkdirs();
			if(!succ){
				throw new IOException("创建文件夹失败 " + fileDir);
			}
		}
		logger.debug("------------->>file：" + filePath + File.separator + fileName);
		FileWriter fw = new FileWriter(new File(filePath, fileName));
		try{
			fw.write(content);	
		}finally {
			fw.close();
		}
		return fw;
	}

	/**
	 * 执行
	 * @throws Exception 
	 */
	public void execute() throws Exception {		
		if (this.template == null) {
			this.template = new TemplateConfig();
		}
		// 获取模板引擎
		logger.debug("------------->> 1、 加载并初始化模板");
		Map<String, Template> templates = this.getTemplate().initTemplates(this.packageInfo);
		logger.debug("------------->> 2、连接数据源");
		SQLManager sqlManager = this.dataSource.getSQLManager(this.strategy.getNc());		
		logger.debug("------------->> 3、读取库里所有表");
		Set<String> tables = sqlManager.getMetaDataManager().allTable();
		String[] include = this.strategy.getInclude();
		String[] exclude = this.strategy.getExclude();
		if (include != null && exclude != null) {
			throw new RuntimeException("include和exclude只能二选一");
		}
		logger.debug("------------->> 4、过滤（指定）表");
		Set<String> genTables = new HashSet<>();
		for (String tableName : tables) {
			if (this.dataSource.getDbType().equals(DBType.ORACLE)) {
				tableName = tableName.toUpperCase();
			}
			if (include != null && include.length > 0) {
				for (String tb : include) {
					if (tableName.equals(tb)) {
						genTables.add(tableName);
					}
				}
			} else {
				if (exclude != null && exclude.length > 0) {
					for (String tb : exclude) {
						if (!tableName.equals(tb)) {
							genTables.add(tableName);
						}
					}
				} else {
					genTables.add(tableName);
				}
			}			
		}
		logger.debug("------------->> 5、初始化模板参数");
		Map<String, Map<String, Object>> allCfg = initTemplateCfgs(sqlManager, genTables);
		logger.debug("------------->> 6、开始生成代码");
		genFile(templates, genTables, allCfg);
		logger.debug("------------->> 7、代码生成结束");

	}

	private String getMethodName(String name) {
		if (name.length() == 1) {
			return name.toUpperCase();
		}
		char ch1 = name.charAt(0);
		char ch2 = name.charAt(1);
		if (Character.isLowerCase(ch1) && Character.isUpperCase(ch2)) {
			//aUname---> getaUname();
			return name;
		} else if (Character.isUpperCase(ch1) && Character.isUpperCase(ch2)) {
			//ULR --> getURL();
			return name;
		} else {
			//general  name --> getName()
			char upper = Character.toUpperCase(ch1);
			return upper + name.substring(1);
		}
	}

	/***
	 * 加载btsql自定义配置
	 * @return
	 */
	private Properties loadDefaultProperties () {
		Properties ps  = new Properties();
		InputStream ins = this.getClass().getResourceAsStream("/btsql.properties");
		if (ins == null) {
			return ps;
		}
		try {
			ps.load(ins);
		} catch (IOException e) {
			throw new RuntimeException("默认配置文件加载错:/btsql.properties");
		}
		return ps;
	}

}
